package com.mlx.mbg.code;

import com.mlx.mbg.code.inter.EnableMethod;
import com.mlx.mbg.code.inter.IExtensionMethod;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.logging.Log;
import org.mybatis.generator.logging.LogFactory;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;


public class MbgUtil {

    private static final Log logger = LogFactory.getLog(MbgUtil.class);

    private static final String interfaceExpressionFormat = "(([A-Za-z_][A-Za-z0-9_]{0,})(,[A-Za-z_][A-Za-z0-9_]{0,}){0,})(;([A-Za-z_][A-Za-z0-9_]{0,})(,[A-Za-z_][A-Za-z0-9_]{0,}){0,}){0,}";

    private static final String selectListQueryExpressionFormat = "([A-Za-z_][A-Za-z0-9_]{0,})(,([A-Za-z_][A-Za-z0-9_]{0,}){0,}){0,}";

    public static boolean isEmpty(Object obj){
        if (obj == null){
            return true;
        }

        if (obj instanceof String){
            return ((String) obj).trim().isEmpty();

        }

        if (obj instanceof Collection){

            return ((Collection<?>) obj).isEmpty();

        }

        if (obj instanceof Map){

            return ((Map<?, ?>) obj).isEmpty();

        }

        if (obj instanceof Object[]){

            return ((Object[]) obj).length == 0;

        }

        return false;
    }

    public static boolean isNotEmpty(Object obj){
        return !(isEmpty(obj));
    }

    /**
     * 扫描获取某个包下的Class对象
     * @param packageName 扫描包包名
     * @param scanChildPackage 是否扫描子包
     * @param predicate 断言函数
     * @return Class对象列表
     */
    public static List<Class<?>> packageScanClass(String packageName, boolean scanChildPackage, Predicate<Class<?>> predicate) {

        // 获取类加载器（当前线程类加载器）
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        Enumeration<URL> packageResources;

        // 返回数据
        List<Class<?>> classList = new ArrayList<>();

        try {
            packageResources = classLoader.getResources(packageName.replaceAll("\\.", "/"));
        }catch (IOException e) {
            throw new RuntimeException("加载包资源发生异常，异常信息：" + e.getMessage());
        }

        while (packageResources.hasMoreElements()){

            // 获取资源包
            URL packageResourceURL = packageResources.nextElement();

            if (packageResourceURL == null){
                throw new RuntimeException(String.format("资源包【%s】不存在或不是包名", packageName));
            }

            // 协议，加载包名的时候有file和jar两种。直接在IDE里运行此方法协议是file，如果是项目打成jar包，执行java -jar执行到此方法是，协议是jar
            String protocol = packageResourceURL.getProtocol();

            if ("file".equals(protocol)){

                // 类文件
                List<File> classFileList = new ArrayList<>();
                // 子包
                List<File> childPackageFileList = new ArrayList<>();

                // 包文件
                File packageFile = new File(packageResourceURL.getFile());

                // 包文件下的所有文件
                File[] classFiles = packageFile.listFiles();
                if (classFiles == null || classFiles.length == 0){
                    return classList;
                }

                // 遍历包文件下的所有文件，获取类文件和子包
                for (File file : classFiles){
                    if (file.isFile() && file.getName().endsWith(".class")){
                        classFileList.add(file);
                    }else if (file.isDirectory()){
                        childPackageFileList.add(file);
                    }
                }

                // 将类文件加载为Class对象
                for (File classFile: classFileList) {
                    String className = packageName + "." + classFile.getName().replaceAll("\\.class", "");
                    try {
                        Class<?> clazz = Class.forName(className);
                        if (predicate == null){ // 断言为空，直接加入返回数据列表
                            classList.add(clazz);
                        }else {
                            if (predicate.test(clazz)){ // 断言不空，断言为True，则加入数据列表
                                classList.add(clazz);
                            }
                        }
                    }catch (NoClassDefFoundError e){
                        String relyClass = e.getMessage();
                        if (relyClass == null){
                            relyClass = "";
                        }
                        relyClass = relyClass.replace("/", ".");
                        System.out.printf("警告信息：类型【%s】无法加载，原因是项目中没有这个类依赖的【%s】类%n", className, relyClass);
                    }catch (ClassNotFoundException e) {
                        throw new RuntimeException("类加载异常：没有发现【"+ className + "】类");
                    }
                }

                // 如果不扫描子包，直接返回，否则继续扫描子包
                if (!scanChildPackage){
                    return classList;
                }

                // 继续扫描子包

                // 如果子包列表为空，直接返回
                if (childPackageFileList.isEmpty()){
                    return classList;
                }

                // 遍历扫描子包
                for (File file : childPackageFileList){
                    classList.addAll(packageScanClass(packageName + "." + file.getName(), scanChildPackage, predicate));
                }

            }else if ("jar".equals(protocol)){

                JarURLConnection jarURLConnection;

                try {
                    jarURLConnection =(JarURLConnection) packageResourceURL.openConnection();
                }catch (IOException e) {
                    throw new RuntimeException("获取JarURLConnection异常，异常信息：" + e.getMessage());
                }

                JarFile jarFile;

                try {
                    jarFile = jarURLConnection.getJarFile();
                }catch (IOException e) {
                    throw new RuntimeException("获取JarFile异常，异常信息：" + e.getMessage());
                }

                // jar包中的所有文件及目录条目
                Enumeration<JarEntry> jarEntries = jarFile.entries();

                int packageDeepin = packageName.split("\\.").length;

                while (jarEntries.hasMoreElements()) {
                    JarEntry jarEntry = jarEntries.nextElement();
                    String jarEntryName = jarEntry.getName();
                    if (jarEntry.isDirectory() || !jarEntryName.endsWith(".class")){
                        continue;
                    }

                    String className = jarEntryName.replace(".class", "");
                    className = className.replace("/", ".");

                    if (!className.startsWith(packageName)){
                        continue;
                    }

                    try {
                        Class<?> clazz = Class.forName(className);

                        int classDeepin = className.split("\\.").length;

                        if (classDeepin == (packageDeepin+1) || scanChildPackage){
                            if (predicate == null){ // 断言为空，直接加入返回数据列表
                                classList.add(clazz);
                            }else {
                                if (predicate.test(clazz)){ // 断言不空，断言为True，则加入数据列表
                                    classList.add(clazz);
                                }
                            }
                        }
                    }catch (NoClassDefFoundError e){
                        String relyClass = e.getMessage();
                        if (relyClass == null){
                            relyClass = "";
                        }
                        relyClass = relyClass.replace("/", ".");
                        System.out.printf("警告信息：类型【%s】无法加载，原因是项目中没有这个类依赖的【%s】类%n", className, relyClass);
                    }catch (ClassNotFoundException e) {
                        throw new RuntimeException("类加载异常：没有发现【"+ className + "】类");
                    }

                }
            }else {
                throw new RuntimeException("不支持的类扫描的URL协议");
            }
        }

        return classList;
    }


    public static boolean isHaveAnnotation(Class<?> clazz, Class<? extends Annotation> annotationType){
        if (clazz == null || annotationType == null){
            throw new RuntimeException("判断类型包含注解方法异常，原因是类型或注解为空");
        }

        for (Annotation clazzAnnotation : clazz.getAnnotations()) {
            if (clazzAnnotation.annotationType() == annotationType){
                return true;
            }
        }

        return false;
    }

    public static boolean checkPrimaryKey(IntrospectedTable introspectedTable, String methodName){
        String tableName = introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime();
        List<IntrospectedColumn> keyColumnList = introspectedTable.getPrimaryKeyColumns();
        if (MbgUtil.isEmpty(keyColumnList)){
            logger.error(String.format("【%s】表没有主键，不生成【%s】方法", tableName, methodName));
            return false;
        }
        return true;
    }

    public static boolean checkOnePrimaryKey(IntrospectedTable introspectedTable, String methodName){
        List<IntrospectedColumn> primaryKeyColumnList = introspectedTable.getPrimaryKeyColumns();
        if (primaryKeyColumnList == null || primaryKeyColumnList.size() != 1){
            logger.error(String.format("【%s表】无法生成【%s】方法，【%s】方法要求主键必须有且必须是单主键，请检查您的主键配置", introspectedTable.getFullyQualifiedTableNameAtRuntime(), methodName, methodName));
            return false;
        }
        return true;
    }

    public static boolean checkInterfaceExpressionFormat(IntrospectedTable introspectedTable, String methodName, String propertyConfigName){
        String tableName = introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime();
        String multiplePropertySelectString = introspectedTable.getTableConfigurationProperty(propertyConfigName);

        if (MbgUtil.isEmpty(multiplePropertySelectString)){
            return false;
        }
        if (!(multiplePropertySelectString.matches(interfaceExpressionFormat))){
            logger.error(String.format("【%s】表的表配置中【%s】属性配置为非法格式，不生成【%s】方法%n", tableName, propertyConfigName, methodName));
            return false;
        }

        String[] multiplePropertySelectColumnStrings = multiplePropertySelectString.split(";");
        for (String multiplePropertySelectColumnString : multiplePropertySelectColumnStrings){
            String[] multiplePropertySelectColumns = multiplePropertySelectColumnString.split(",");
            for (String multiplePropertySelectColumn : multiplePropertySelectColumns){
                IntrospectedColumn column = introspectedTable.getColumn(multiplePropertySelectColumn);
                if (MbgUtil.isEmpty(column)){
                    logger.error(String.format("【%s】表生成【%s】方法失败，原因是表配置中的【%s】属性配置指定的【%s】字段不存在%n", tableName, methodName, propertyConfigName, multiplePropertySelectColumn));
                    return false;
                }
            }
        }
        return true;
    }

    public static boolean checkSelectListQueryExpressionFormat(IntrospectedTable introspectedTable, String methodName, String propertyConfigName){
        String tableName = introspectedTable.getAliasedFullyQualifiedTableNameAtRuntime();
        String selectListQueryString = introspectedTable.getTableConfigurationProperty(propertyConfigName);

        if (MbgUtil.isEmpty(selectListQueryString)){
            return true;
        }
        if (!(selectListQueryString.matches(selectListQueryExpressionFormat))){
            logger.error(String.format("【%s】表的表配置中【%s】属性配置为非法格式，不生成【%s】方法%n", tableName, propertyConfigName, methodName));
            return false;
        }


        String[] selectListQueryColumns = selectListQueryString.split(",");
        for (String selectListQueryColumn : selectListQueryColumns){
            IntrospectedColumn column = introspectedTable.getColumn(selectListQueryColumn);
            if (MbgUtil.isEmpty(column)){
                logger.error(String.format("【%s】表生成【%s】方法失败，原因是表配置中的【%s】属性配置指定的【%s】字段不存在%n", tableName, methodName, propertyConfigName, selectListQueryColumn));
                return false;
            }
        }

        return true;
    }

    public static void main(String[] args) {
        List<IExtensionMethod> extensionMethodList = new ArrayList<>();
        List<Class<?>> classList = MbgUtil.packageScanClass(
                "com.mlx.mbg.code.method",
                false,
                clazz -> IExtensionMethod.class.isAssignableFrom(clazz) && MbgUtil.isHaveAnnotation(clazz, EnableMethod.class));

        for (Class<?> clazz : classList){
            try {
                IExtensionMethod instance =(IExtensionMethod) clazz.newInstance();
                extensionMethodList.add(instance);
            } catch (InstantiationException e) {
                throw new RuntimeException(String.format("扩展方法接口【%s】无参构造方法执行异常", clazz.getName()));
            } catch (IllegalAccessException e) {
                throw new RuntimeException(String.format("没有扩展方法接口【%s】无参构造方法访问权限", clazz.getName()));
            }
        }

        extensionMethodList.sort(Comparator.comparing(IExtensionMethod::getOrder));

        extensionMethodList.forEach(obj -> System.out.println(obj.getClass()));
        System.out.println("aaaaaaa");
    }
}
